package com.dap.utils;

/**
 * @author wangmin
 * @discription 身份证号工具类
 * @created 2019-09-27 9:23
 */
public abstract class IdentityNumberUtil {
    /**
     * 一代身份证转化为数值最小值
     * */
    private static final long minValueOfFirstID = convertIdentityNumberStrToLong("110000"+"00"+"0101"+"000");
    /**
     * 一代身份证转化为数值最大值
     * */
    private static final long maxValueOfFirstID = convertIdentityNumberStrToLong("659999"+"99"+"1231"+"999");
    /**
     * 二代身份证转化为数值最小值
     * */
    private static final long minValueOfSecondID = convertIdentityNumberStrToLong("110000"+"1830"+"0101"+"000X");
    /**
     * 二代身份证转化为数值最大值
     * */
    private static final long maxValueOfSecondID = convertIdentityNumberStrToLong("659999"+"9999"+"1231"+"9999");


    private static final int[] ratioValueArray = {7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};
    private static final char[] matchCheckValueArray = {'1','0','X','9','8','7','6','5','4','3','2'};
    private static boolean validateCheckNum(String identityNumber) {
        if (18 == identityNumber.length()) {
            int sum = 0;
            for (int i=0; i<17; ++i) {
                sum += (identityNumber.charAt(i)-'0') * ratioValueArray[i];
            }
            return matchCheckValueArray[sum%11] == identityNumber.charAt(17);
        } else {
            return true;
        }
    }
    /**
     * 验证 身份证字符串 的合法性
     * */
    public static boolean validateIdentityNumber(String identityNumber) {
        if (null == identityNumber) {
            return false;
        }
        if (18 != identityNumber.length() && 15 != identityNumber.length()) {
            return false;
        }

        // 验证字符
        final int validateLen = (18==identityNumber.length() && 'X'==identityNumber.charAt(17)) ? 17 : identityNumber.length();
        for (int i=0; i<validateLen; ++i) {
            char ch = identityNumber.charAt(i);
            if (ch < '0' || '9' < ch) {
                return false;
            }
        }

        final long regionCode = Integer.parseInt(identityNumber.substring(0, 6));
        if (regionCode < 110000 || 659999 < regionCode) {
            return false;
        }

        final int month, day;
        if (18 == identityNumber.length()) {
            final int year = Integer.parseInt(identityNumber.substring(6, 10));
            if (year < 1830) {
                return false;
            }
            month = Integer.parseInt(identityNumber.substring(10, 12));
            day = Integer.parseInt(identityNumber.substring(12, 14));
        } else {
            month = Integer.parseInt(identityNumber.substring(8, 10));
            day = Integer.parseInt(identityNumber.substring(10, 12));
        }
        if (month < 1 || 12 < month) {
            return false;
        }
        if (day < 1 || 31 < day) {
            return false;
        }

        return validateCheckNum(identityNumber);
    }

    /**
     * 将 身份证字符串 转化为 long类型数值
     * */
    public static long convertIdentityNumberStrToLong(String identityNumber) {
        /**
         * 地址码的范围是: 110000~659999
         * 减去 100000 范围是 010000~559999
         * 可使用16个bit位表示
         * */
        final long regionCode = Integer.parseInt(identityNumber.substring(0, 6)) - 100000;

        if (18 == identityNumber.length()) {
            /**
             * 出生年范围为 1830~9999
             * 转化之后出生年月日数值范围为 : 0~3137278‬
             * 可使用22个bit位表示
             * */
            final int year = Integer.parseInt(identityNumber.substring(6, 10)) - 1830;
            final int month = Integer.parseInt(identityNumber.substring(10, 12)) - 1;
            final int day = Integer.parseInt(identityNumber.substring(12, 14)) - 1;
            final long date = (year*12 + month)*32 + day;

            /**
             * 顺序码 格式为 0000~9999 或者 000X~999X
             * 转化后 数值范围为 0~19999
             * 可使用15个bit位表示
             * */
            final long serialCode;
            if ('X' == identityNumber.charAt(17)) {
                serialCode = Integer.parseInt(identityNumber.substring(14, 17));
            } else {
                serialCode = Integer.parseInt(identityNumber.substring(14, 18)) + 10000;
            }

            return (regionCode << 37) | (date << 15) | (serialCode);
        } else if (15 == identityNumber.length()) {
            /**
             * 出生年范围为 00~99
             * 转化之后出生年月日数值范围为 : 0~38016
             * 可使用16个bit位表示
             * */
            final int year = Integer.parseInt(identityNumber.substring(6, 8));
            final int month = Integer.parseInt(identityNumber.substring(8, 10)) - 1;
            final int day = Integer.parseInt(identityNumber.substring(10, 12)) - 1;
            final long date = (year*12 + month)*32 + day;

            /**
             * 顺序码 格式为 000~999
             * 可使用10个bit位表示
             * */
            final long serialCode = Integer.parseInt(identityNumber.substring(12, 15));

            return (regionCode << 26) | (date << 10) | (serialCode);
        }

        return 0;
    }

    private static String dateIntToStr(final int date, final int startYear, final int yearStrLength) {
        final int day = (date&31) + 1;
        final int monthAndYear = (date - (day-1)) >> 5;
        final int month = monthAndYear % 12 + 1;
        final int year = monthAndYear / 12 + startYear;
        return String.format("%0"+yearStrLength+"d%02d%02d", year, month, day);
    }

    /**
     * 将 long类型数值 转化为 身份证字符串
     * */
    public static String convertLongToIdentityNumberStr(long value) {
        if (minValueOfSecondID <= value && value <= maxValueOfSecondID) {
            final long regionCode = value >> 37;

            final int date = (int) (value >> 15) & ((1<<22)-1);
            final String dateStr = dateIntToStr(date, 1830, 4);

            final long serialCode = value & ((1<<15)-1);
            final String serialCodeStr;
            if (serialCode >= 10000) {
                serialCodeStr = String.format("%04d", serialCode-10000);
            } else {
                serialCodeStr = String.format("%03dX", serialCode);
            }

            return String.format("%d%s%s", regionCode+100000, dateStr, serialCodeStr);
        } else if (minValueOfFirstID <= value && value <= maxValueOfFirstID) {
            final long regionCode = value >> 26;

            final int date = (int) (value >> 10) & ((1<<16)-1);
            final String dateStr = dateIntToStr(date, 0, 2);

            final long serialCode = value & ((1<<10)-1);

            return String.format("%d%s%03d", regionCode+100000, dateStr, serialCode);
        } else {
            throw new RuntimeException("invalid value" + value);
        }
    }


    public static void main(String[] args) {
        String[] array = {
            "110000"+"00"+"0101"+"000",
            "659999"+"00"+"0101"+"000",
            "659999"+"99"+"1231"+"999",
            "110000"+"1830"+"0101"+"0000",
            "110000"+"1830"+"0101"+"9999",
            "110000"+"1830"+"0101"+"000X",
            "659999"+"1830"+"0101"+"000X",
            "659999"+"9999"+"1231"+"999X",
            "659999"+"9999"+"1231"+"0000",
            "659999"+"9999"+"1231"+"9999"
        };

        for (String sourceID: array) {
            boolean validateResult = validateIdentityNumber(sourceID);
            System.out.println("validate = " + validateResult);
            if (validateResult) {
                long l = convertIdentityNumberStrToLong(sourceID);
                //System.out.println("long= " + l);
                String convertedId = convertLongToIdentityNumberStr(l);
                //System.out.println("id  = " + convertedId);
                System.out.println("equals = " + sourceID.equals(convertedId));
            }
        }
    }
}
